package com.alinesno.cloud.common.web.login.shiro;

import java.util.concurrent.TimeUnit;

import org.apache.commons.codec.digest.Md5Crypt;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

/**
 * 验证器,添加登陆次数校验功能
 * 
 * @author WeiXiaoJin
 * @since 2019年11月19日 上午7:34:38
 */
public class RetryLimitHashedCredentialsMatcher extends SimpleCredentialsMatcher {
	
	@SuppressWarnings("unused")
	private static final Logger log = LoggerFactory.getLogger(RetryLimitHashedCredentialsMatcher.class);
	
	private int maxRetryNum;
	private int lockTime = 60 ;
	
	private static final String SALT_KEY = "alinesno" ; 

	/**
	 * 用户登录次数计数 redisKey 前缀
	 */
	private static final String SHIRO_LOGIN_COUNT = "shiro_login_count_";
	/**
	 * 用户登录是否被锁定 一小时 redisKey 前缀
	 */
	private static final String SHIRO_IS_LOCK = "shiro_is_lock_";

	@Autowired
	private RedisTemplate<String, String> redisTemplate;

	@Override
	public boolean doCredentialsMatch(AuthenticationToken authenticationToken, AuthenticationInfo info) {
		String username = (String) authenticationToken.getPrincipal();

		// 访问一次，计数一次
		ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
		String loginCountKey = SHIRO_LOGIN_COUNT + Md5Crypt.apr1Crypt(username, SALT_KEY) ; 
		String isLockKey = SHIRO_IS_LOCK + Md5Crypt.apr1Crypt(username, SALT_KEY);
		opsForValue.increment(loginCountKey, 1);

		if (redisTemplate.hasKey(isLockKey)) {
			throw new LockedAccountException("帐号[" + username + "]已被禁止登录！");
		}

		// 计数大于5时，设置用户被锁定一小时
		String loginCount = String.valueOf(opsForValue.get(loginCountKey));
		int retryCount = (maxRetryNum - Integer.parseInt(loginCount));
		if (retryCount <= 0) {
			opsForValue.set(isLockKey, "LOCK");
			redisTemplate.expire(isLockKey, lockTime , TimeUnit.MINUTES);
			redisTemplate.expire(loginCountKey, lockTime , TimeUnit.MINUTES);
			throw new ExcessiveAttemptsException("由于密码输入错误次数过多，帐号[" + username + "]已被禁止登录！");
		}

		boolean matches = super.doCredentialsMatch(authenticationToken, info);
		if (!matches) {
			String msg = retryCount <= 0 ? "您的账号一小时内禁止登录！" : "您还剩" + retryCount + "次重试的机会";
			throw new ExcessiveAttemptsException("帐号或密码不正确！" + msg);
		}

		// 清空登录计数
		redisTemplate.delete(loginCountKey);

		return true;
	}

	public void setMaxRetryNum(int maxRetry) {
		this.maxRetryNum = maxRetry ;
	}

}